//---STD-------------------------------------------------------------
#include <iostream>
#include <fstream>

//---SShader---------------------------------------------------------
#include "CGlsl.h"

//---Using-----------------------------------------------------------
using std::cerr;
using namespace SShader;

//-------------------------------------------------------------------

/**
  @brief NE DOIS PAS ÊTRE UTILISÉ
*/
GlslShader::GlslShader()
{
  isInitVAO = false ;
  normalizeVertex = false ;
  isVertexLink = false ;
}

/**
  @param _sVertex : Nom du fichier du Vertex shader (.vert)
  @param _sPixel : Nom du fichier du Fragment shader (.frag)
*/
GlslShader::GlslShader(string _sVertex, string _sPixel) : CShader(_sVertex, _sPixel)
{
  isInitVAO = false ;
  normalizeVertex = false ;
  isVertexLink = false ;
}

GlslShader::GlslShader(const CShader &_shader) : 
CShader(_shader) 
{
  isInitVAO = false ;
  normalizeVertex = false ;
  isVertexLink = false ;
}

/**
  @brief Destructeur
*/
GlslShader::~GlslShader()
/*~CShader() */{

  map<string, map<int, int> >::iterator itName ;
  for (itName=vNameUniform.begin() ; itName!=vNameUniform.end() ; itName++) {
    vNameUniform[itName->first].clear() ;
  }

  unsigned int id ;
  map<string, int* > ::iterator it ;
  for (it = vNameVertex.begin() ; it != vNameVertex.end() ; ++it) {
    id =(unsigned int)(it->second[4]) ;
    glDeleteBuffers(1, &id) ;
    delete[] (it->second) ;
  }

  vNameVertex.clear() ;
  vNameUniform.clear() ;
  vDataUniform.clear() ;
  vTextureId.clear() ;

  glDeleteVertexArrays(1, &iVAO) ;
}

/**
  @brief constructeur par copie \n (copie indépendante sauf pour les pointeurs vers les données reste biensûr identique)
*/
GlslShader::GlslShader(const GlslShader &copie):
CShader(copie)
{

  map<string, map<int, int> >::iterator itName ;
  for (itName=vNameUniform.begin() ; itName!=vNameUniform.end() ; itName++) {
    vNameUniform[itName->first][itName->second.begin()->first] = itName->second.begin()->second ;
  }

  map<string, void*>::iterator itData ;
  for (itData=vDataUniform.begin() ; itData!=vDataUniform.end() ; itData++) {
    vDataUniform[itData->first]=itData->second ;
  }

  map<int, unsigned int>::iterator itTexture ;
  for (itTexture=vTextureId.begin() ; itTexture!=vTextureId.end() ; itTexture++) {
    vTextureId[itTexture->first]=itTexture->second ;
  }
}

/**
  @brief Ajouter une donnée Uniform
  @param _sName : Nom de la variable
  @param _iCount : Nombre d'éléments (taille tableau de donné)
  @param _sType : Type de la donnée (Enum membre : FLOAT, INT ..)
  @param _Data : Donnée casté en void*
*/
void GlslShader::addDonneUniform(string _sName, int _iCount, int _sType, void* _Data) {
  if (vNameUniform.find(_sName) !=vNameUniform.end()) {
    vNameUniform[_sName].clear() ;
  }
  vNameUniform[_sName][_iCount] = _sType ;
  vDataUniform[_sName] = _Data ;
}


/**
  @brief Ajouter une donnée Vertex
  @param _sName : Nom de la variable
  @param _miseajour :  Type de mise à jour [ STREAM_DRAW DYNAMIC_DRAW STATIC_DRAW ]
  @param _dimention : Dimention des vertex 3D ou 2D
  @param _iCount : Nombre d'éléments (taille tableau de donné)
  @param _iTypeBuffer : Type de buffer (exemple : GL_ARRAY_BUFFER, GL_ELEMENT_ARRAY_BUFFER) ;
  @param _sType : Type de la donnée (Enum membre : FLOAT, INT ..)
  @param _Data : Donnée casté en void*
*/
void GlslShader::addDonneVertex(string *_sName, GLsizei *_stride, int *_iDebut, int *_iDimention,int _iNbElement, int _iMiseajour,int _iTypeBuffer, int _iType, int iCount, void* _Data ) {
  unsigned int iGenBuffer ;
  glGenBuffers (1, &iGenBuffer);
  //Chaque VertexAttribut
  for (int i = 0 ; i < _iNbElement ; ++i) {
    vNameVertex[_sName[i]] = new int [6] ;
    int *valeur = vNameVertex[_sName[i]] ;
    valeur[0] = _stride[i] ;
    valeur[1] = _iDebut[i] ;
    valeur[2] = _iDimention[i] ;
    valeur[3] = _iType ;
    valeur[4] = (int)iGenBuffer ;
  }
  //Allocation de la memoir1e Graphic 
  glBindBuffer(_iTypeBuffer, iGenBuffer);
  glBufferData(_iTypeBuffer, sizeof(_iType)*iCount, _Data, _iMiseajour) ;
}

/**
  @brief Ajouter une donnée Fragment
  @param _sName : Nom de la variable
  @param _iType :     Specifies the texture target. Must be one of the following symbolic constants:
                      - GL_TEXTURE_2D,
                      - GL_TEXTURE_CUBE_MAP_POSITIVE_X
                      - GL_TEXTURE_CUBE_MAP_NEGATIVE_X
                      - GL_TEXTURE_CUBE_MAP_POSITIVE_Y
                      - GL_TEXTURE_CUBE_MAP_NEGATIVE_Y
                      - GL_TEXTURE_CUBE_MAP_POSITIVE_Z
                      - GL_TEXTURE_CUBE_MAP_NEGATIVE_Z
  @param _iIdData : Id Texture 
  @param _ilevel : Specifies the mipmap level of the texture image to be attached, which must be 0.
*/
void GlslShader::addDonneFragment(string _sName, int _iType, int _iIdData, int _ilevel=0) {
  vNameFragment[_sName] = new int [3] ;
  int *valeur = vNameFragment[_sName] ;
  valeur[0] = _iType ;
  valeur[1] = _iIdData ;
  valeur[2] = _ilevel ;
}

/**
  @brief Ajouter une donnée Uniform Matrix
  @param _sName : Nom de la variable
  @param _iCountWidth : Nombre d'éléments (taille tableau de donné)
  @param _iCountHeight : Nombre d'éléments (taille tableau de donné)
  @param _sType : Type de la donnée (Enum membre : FLOAT, INT MATRIX)
  @param _Data : Donnée casté en void*
*/
void GlslShader::addDonneMatrix(string _sName, int _iCountWidth, int _iCountHeight, float* _Data ) {
  if (vNameUniform.find(_sName) !=vNameUniform.end()) {
    vNameUniform[_sName].clear ();
  }
  if (_iCountWidth == _iCountHeight) {
    vNameUniform[_sName][_iCountWidth*_iCountHeight] = MATRIX ;
    vDataUniform[_sName] = _Data ;
  } else if (_iCountWidth > _iCountHeight) {
    vNameUniform[_sName][_iCountWidth*_iCountHeight] = MATRIX_X ;
    vDataUniform[_sName] = _Data ;
  }else {
    vNameUniform[_sName][_iCountWidth*_iCountHeight] = MATRIX_Y ;
    vDataUniform[_sName] = _Data ;
  }
}

/**
  @brief Supprime une donnée Uniform
  @param _sName : Nom de la variable
*/
void GlslShader::removeDonne(string _sName) {
  if (vNameUniform.erase(_sName) > 0) {
    vDataUniform.erase(_sName) ;
  }else if ( vNameVertex.find(_sName) != vNameVertex.end() ) {
    unsigned int id ;
    map<string, int* > ::iterator it ;
    for (it = vNameVertex.begin() ; it != vNameVertex.end() ; ++it) {
      id =(unsigned int)(it->second[4]) ;
      glDeleteBuffers(1, &id) ;
      delete[] (it->second) ;
    }
  }else {
    vNameFragment.erase(_sName) ;
  }
}

/**
  @brief génère les attributes pour le sommet
*/
void GlslShader::useAttribute () {
  map<string, map<int, int> >::iterator it ;
  // Set up initial attribute valeurs
  for (it=vNameUniform.begin()  ; it!=vNameUniform.end() ; it++) {
      CreateGlAttribute(it->first, vDataUniform[it->first], it->second) ;
  }
}

/**
  @brief Ajouter un identifiant texture
  @param _NumberArb : Numero de texture glsl
  @param _Id : Identifiant de la texture
*/
void GlslShader::addTextureId(int _NumberArb, unsigned int _Id) {
  vTextureId[_NumberArb]=_Id ;
}

/**
  @brief Supprimer un identifiant texture
  @param _NumberArb : Numero de texture glsl
*/
void GlslShader::removeTextureId(int _NumberArb) {
  vTextureId.erase(_NumberArb) ;
}

/**
  @brief Associe une nom de variable Uniform à la texture
*/
void GlslShader::addName (int _NumberArb, string _sName) {
  addDonneUniform(_sName, 1, INT, (void*)&(vTextureId.find(_NumberArb)->first) ) ;
}

/**
  @brief Supprime le nom associé à la texture
*/
void GlslShader::removeName (string _sName) {
  removeDonne(_sName) ;
}

/**
  @brief Activé le shader
*/
void GlslShader::enableShader() {

  glUseProgram(*m_uiProgramShader);

  if (isVertexLink == false) {
    linkVertexName () ;
    isVertexLink = true ;
  }

  if (isInitVAO == false) {
    VertexAttribPointer () ;
  } else {
    glBindVertexArray (iVAO);
  } 

  map<string, map<int, int> >::iterator it ;
  // Set up initial uniform valeurs
  for (it=vNameUniform.begin()  ; it!=vNameUniform.end() ; ++it) {
      CreateGlUniform(it->first, vDataUniform[it->first], it->second) ;
  }

  map<int, unsigned int>::iterator id ;
  for (id=vTextureId.begin()  ; id!=vTextureId.end() ; id++) {
    glActiveTexture(GL_TEXTURE0+id->first) ;
    glBindTexture(GL_TEXTURE_2D, id->second);
  }

}

/**
  @brief Désactiver le shader
*/
void GlslShader::disableShader() {
  //Eteint VA
  glBindVertexArray (0);
  //Eteint Vertex Attribut
  int *valeur = NULL ;
  map<string, int* > ::iterator itVertex ;
  for (itVertex = vNameVertex.begin() ; itVertex != vNameVertex.end() ; ++itVertex) {
    valeur = itVertex->second ;
    glDisableVertexAttribArray ((unsigned int)valeur[5]) ;
  }
  //Eteint shader
  glUseProgram(0);
}

/**
  @brief Génère un glUniform adapté à la donnée
  @param _Name : Nom de la variable uniform
  @param _Data : Donnée (valeur du vDataUniform)
  @param _Type : Description de la donnée (valeur du vNameUniform)
*/
void GlslShader::CreateGlUniform (string _Name, void* _Data,  map<int, int> _Type) {


  int nb=(_Type.begin())->first ;
  int type=(_Type.begin())->second ;

  GLint _loc = glGetUniformLocation(*m_uiProgramShader, _Name.c_str() ) ;
//   if (_loc == -1) {
//     cerr<<"Il n'y a pas de variable avec ce nom \""<<_Name<<"\" dans les shaders \n" ;
//     return;
//   }

  switch (nb) {
    case 1:
      switch (type) {
        case (INT):
          glUniform1iv(_loc, nb, (GLint*)_Data) ;
        break ;

        case (FLOAT):
          glUniform1fv(_loc, nb, (GLfloat*)_Data) ;
        break ;
      }
    break ;

    case 2:
      switch (type) {
        case (INT):
          glUniform2iv(_loc, nb, (GLint*)_Data) ;
        break ;

        case (FLOAT):
          glUniform2fv(_loc, nb, (GLfloat*)_Data) ;
        break ;
      }
    break ;

    case 3:
      switch (type) {
        case (INT):
          glUniform3iv(_loc, nb, (GLint*)_Data) ;
        break ;

        case (FLOAT):
          glUniform3fv(_loc, nb, (GLfloat*)_Data) ;
        break ;
      }
    break ;

    case 4:
      switch (type) {
        case (MATRIX):
          glUniformMatrix2fv(_loc, 1, false, (GLfloat*)_Data) ;
        break ;

        case (INT):
          glUniform4iv(_loc, nb, (GLint*)_Data) ;
        break ;

        case (FLOAT):
          glUniform4fv(_loc, nb, (GLfloat*)_Data) ;
        break ;
      }
    break ;

    case 6:
      switch (type) {
        case (MATRIX_Y):
          glUniformMatrix2x3fv(_loc, 1, false, (GLfloat*)_Data) ;
        break ;

        case (MATRIX_X):
          glUniformMatrix3x2fv(_loc, 1, false, (GLfloat*)_Data) ;
        break ;
      }
    break ;

    case 8:
      switch (type) {
        case (MATRIX_Y):
          glUniformMatrix2x4fv(_loc, 1, false, (GLfloat*)_Data) ;
        break ;

        case (MATRIX_X):
          glUniformMatrix4x2fv(_loc, 1, false, (GLfloat*)_Data) ;
        break ;
      }
    break ;

    case 9:
      switch (type) {
        case (MATRIX):
          glUniformMatrix3fv(_loc, 1, false, (GLfloat*)_Data) ;
        break ;
      }
    break ;

    case 12:
      switch (type) {
        case (MATRIX_Y):
          glUniformMatrix3x4fv(_loc, 1, false, (GLfloat*)_Data) ;
        break ;

        case (MATRIX_X):
          glUniformMatrix4x3fv(_loc, 1, false, (GLfloat*)_Data) ;
        break ;
      }
    break ;
    case 16:
      switch (type) {
        case (MATRIX):
          glUniformMatrix4fv(_loc, 1, false, (GLfloat*)_Data) ;
        break ;
      }
    break ;
  }

}

/**
  @brief Génère un glAttribute adapté à la donnée
  @warning NE GÈRE PAS LES MATRICES
  @param _Name : Nom de la variable attribute
  @param _Data : Donnée (valeur du vDataUniform)
  @param _Type : Description de la donnée (valeur du vNameUniform)
*/
void GlslShader::CreateGlAttribute (string _Name, void* _Data,  map<int, int> _Type) {

  int nb=(_Type.begin())->first ;
  int type=(_Type.begin())->second ;

  //Ignore les Uniform
  if (UNIFORM < ATTRIBUTE) {
    if (type < ATTRIBUTE) {
      return ;
    }
  }else {
    if (type >= UNIFORM) {
      return ;
    }
  }

  GLint _loc = glGetAttribLocation(*m_uiProgramShader, _Name.c_str() ) ;
  if (_loc == -1) {
    cerr<<"Il n'y a pas de : \"" ;
    switch (_Type.begin()->second) {
      case (UNIFORM+INT) :
        cerr<<"attribute int " ;
      break ;
    }
    cerr<<_Name<<"\" dans les shaders \n" ;
    return;
  }

  switch (nb) {
    case 1:
      switch (type) {
        case (SHORT+ATTRIBUTE):
          glVertexAttrib1sv(_loc, (GLshort*)_Data) ;
        break ;

        case (FLOAT+ATTRIBUTE):
          glVertexAttrib1fv(_loc, (GLfloat*)_Data) ;
        break ;

        case (DOUBLE+ATTRIBUTE):
          glVertexAttrib1dv(_loc, (GLdouble*)_Data) ;
        break ;
      }
    break ;

    case 2:
      switch (type) {
        case (SHORT+ATTRIBUTE):
          glVertexAttrib2sv(_loc, (GLshort*)_Data) ;
        break ;

        case (FLOAT+ATTRIBUTE):
          glVertexAttrib2fv(_loc, (GLfloat*)_Data) ;
        break ;

        case (DOUBLE+ATTRIBUTE):
          glVertexAttrib2dv(_loc, (GLdouble*)_Data) ;
        break ;
      }
    break ;

    case 3:
      switch (type) {
        case (SHORT+ATTRIBUTE):
          glVertexAttrib3sv(_loc, (GLshort*)_Data) ;
        break ;

        case (FLOAT+ATTRIBUTE):
          glVertexAttrib3fv(_loc, (GLfloat*)_Data) ;
        break ;

        case (DOUBLE+ATTRIBUTE):
          glVertexAttrib3dv(_loc, (GLdouble*)_Data) ;
        break ;
      }
    break ;

    case 4:
      switch (type) {

        case (UBYTE+ATTRIBUTE):
          glVertexAttrib4ubv(_loc, (GLubyte*)_Data) ;
        break ;

        case (BYTE+ATTRIBUTE):
          glVertexAttrib4bv(_loc, (GLbyte*)_Data) ;
        break ;

        case (USHORT+ATTRIBUTE):
          glVertexAttrib4usv(_loc, (GLushort*)_Data) ;
        break ;

        case (SHORT+ATTRIBUTE):
          glVertexAttrib4sv(_loc, (GLshort*)_Data) ;
        break ;

        case (UINT+ATTRIBUTE):
          glVertexAttrib4uiv(_loc, (GLuint*)_Data) ;
        break ;

        case (INT+ATTRIBUTE):
          glVertexAttrib4iv(_loc, (GLint*)_Data) ;
        break ;

        case (FLOAT+ATTRIBUTE):
          glVertexAttrib4fv(_loc, (GLfloat*)_Data) ;
        break ;

        case (DOUBLE+ATTRIBUTE):
          glVertexAttrib4dv(_loc, (GLdouble*)_Data) ;
        break ;
      }
    break ;
  }
  /*
    void glVertexAttrib4Nbv(	GLuint  	index,
            const GLbyte * 	v);
    void glVertexAttrib4Nsv(	GLuint  	index,
            const GLshort * 	v);
    void glVertexAttrib4Niv(	GLuint  	index,
            const GLint * 	v);
    void glVertexAttrib4Nubv(	GLuint  	index,
            const GLubyte * 	v);
    void glVertexAttrib4Nusv(	GLuint  	index,
            const GLushort * 	v);
    void glVertexAttrib4Nuiv(	GLuint  	index,
            const GLuint * 	v);
  */
}


/**
  @brief Génère un glUniform adapté à la donnée
  @param _NumberArb : Numero de texture glsl
  @param _Type : Type du tableau @see eType
  @param _Dimension : Taille du tableau
  @param _Data : Tableau de coordonné
  @deprecated In Opengl 3.1
*/
/* void GlslShader::MultiTexCoord  (int _NumberArb, int _TYPE, int _Dimension, void* _Data) {

double datad[4] = {0., 0., 0., 1.} ;
double* initDatad ;

float dataf[4] = {0., 0., 0., 1.} ;
float* initDataf ;

int datai[4] = {0, 0, 0, 1} ;
int* initDatai ;

short datas[4] = {0, 0, 0, 1} ;
short* initDatas ;


  switch (_TYPE) {
    case FLOAT:
      initDataf = (float*)_Data ;
      for (int i  =0 ; i < _Dimension ; i++) {
        dataf[i] = initDataf[i] ;
      }
      glMultiTexCoord4fvARB(vTextureId[_NumberArb], dataf) ;
    break ;

    case INT:
      initDatai = (int*)_Data ;
      for (int i  =0 ; i < _Dimension ; i++) {
        datai[i] = initDatai[i] ;
      }
      glMultiTexCoord4ivARB(vTextureId[_NumberArb], datai) ;
    break ;

    case SHORT:
      initDatas = (short*)_Data ;
      for (int i  =0 ; i < _Dimension ; i++) {
        datas[i] = initDatas[i] ;
      }
      glMultiTexCoord4svARB(vTextureId[_NumberArb], datas) ;
    break ;

    case DOUBLE:
      initDatad = (double*)_Data ;
      for (int i  =0 ; i < _Dimension ; i++) {
        datad[i] = initDatad[i] ;
      }
      glMultiTexCoord4dvARB(vTextureId[_NumberArb], datad) ;
    break ;

  }
} */

bool GlslShader::changeNormalize () {
  normalizeVertex = !normalizeVertex ;
  return normalizeVertex ;
}

void GlslShader::linkVertexName () {
 unsigned int IdTable ;
  int *valeur = NULL ;
  map<string, int* > ::iterator itVertex ;
  for (itVertex = vNameVertex.begin() ; itVertex != vNameVertex.end() ; ++itVertex) {
    IdTable = glGetAttribLocation (*m_uiProgramShader, itVertex->first.c_str()) ;
    valeur = itVertex->second ;
    valeur[5] = (int)IdTable ;
  }
}

void GlslShader::VertexAttribPointer () {
  int *valeur = NULL ;
  map<string, int* > ::iterator itVertex ;
  for (itVertex = vNameVertex.begin() ; itVertex != vNameVertex.end() ; ++itVertex) {
    valeur = itVertex->second ;
    glEnableVertexAttribArray ((unsigned int)valeur[5]) ;
    glBindBuffer(GL_ARRAY_BUFFER, (unsigned int)valeur[4]);
    glVertexAttribPointer ((unsigned int)valeur[5], valeur[2], valeur[3], normalizeVertex,  valeur[0],  (float *) NULL +valeur[1]) ;
  }
}

void GlslShader::InitialiseVAO () {

  if (isInitVAO == true) {
    glDeleteVertexArrays (1, &iVAO);
  }else {
    isInitVAO = true ;
  }

  if (isVertexLink == false) {
    linkVertexName() ;
    isVertexLink = true ;
  }

  glGenVertexArrays (1, &iVAO);
  glBindVertexArray (iVAO);
    VertexAttribPointer () ;
  glBindVertexArray (0);

}

void GlslShader::FrameBufferTexture() {
  int nb = 0  ;
  GLenum location ;
  int *valeur ;
  GLenum *buffers = new GLenum [vNameFragment.size()] ;
  map<string, int* > ::iterator itFragment ;
  for (itFragment = vNameFragment.begin() ; itFragment != vNameFragment.end() ; ++itFragment) {
    location = glGetFragDataLocation(*m_uiProgramShader, itFragment->first.c_str()) ;
    valeur =  itFragment->second ;
    buffers[nb] = location ;
    ++nb ;
    glFramebufferTexture2D(GL_FRAMEBUFFER, location, valeur[0], valeur[1], valeur[2]);
  }
  glDrawBuffers (2, buffers) ;
  delete[] buffers ;
}

unsigned int GlslShader::getIdBuffer(string _vertexName) {
  return (unsigned int)(vNameVertex[_vertexName][4])     ;
}

